package org.thinginitself.mixpath.dom;

import org.thinginitself.mixpath.Options;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;

/**
 * @author Andrew Harrison
 * @version 1.0.0 16/05/2011
 */
public class DomHelp {

    private DomHelp() {
    }


    public static Document newDocument() {
        Document doc = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.newDocument();
        } catch (ParserConfigurationException e) {
            return null;
        }
        return doc;
    }

    public static Document newDocument(String xml) throws IOException {
        StringReader reader = new StringReader(xml);
        return newDocument(reader);
    }


    public static Document newDocument(InputStream stream) throws IOException {
        Document doc = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);

            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(stream);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        return doc;
    }


    public static Document newDocument(Reader reader) throws IOException {
        Document doc = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);

            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(new InputSource(reader));
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        return doc;
    }

    public static StreamResult write(Node doc, OutputStream out) throws IOException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = null;
        try {
            t = tf.newTransformer();
            t.setOutputProperty(OutputKeys.METHOD, "xml");
            t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            t.setOutputProperty(OutputKeys.STANDALONE, "yes");

        } catch (TransformerConfigurationException tce) {
            assert (false);
        }
        DOMSource doms = new DOMSource(doc);
        StreamResult sr = new StreamResult(out);
        try {
            t.transform(doms, sr);
        } catch (TransformerException te) {
            throw new IOException(te.getMessage());
        }
        return sr;
    }


    public static Document getOwnerDocument(Node node) {
        if (node.getNodeType() == Node.DOCUMENT_NODE) {
            return (Document) node;
        } else {
            return node.getOwnerDocument();
        }
    }

    public static String getPrefix(Node contextNode, String namespace) {
        for (Node n = contextNode; n != null; n = n.getParentNode()) {

            String myNamespace = n.getNamespaceURI();
            if (myNamespace != null && !namespace.equals(myNamespace)) {
                String myPrefix = n.getPrefix();
                if (myPrefix != null && myPrefix.length() > 0) {
                    return myPrefix;
                }
            }
            if (n.hasAttributes()) {
                NamedNodeMap atts = n.getAttributes();
                int length = atts.getLength();
                for (int i = 0; i < length; i++) {
                    Attr att = (Attr) atts.item(i);
                    // work around crimson bug by testing URI rather than name
                    String attributeNamespace = att.getNamespaceURI();
                    if ("http://www.w3.org/2000/xmlns/".equals(attributeNamespace)) {
                        String val = att.getNodeValue();
                        if (namespace.equals(val)) {
                            String pref = att.getLocalName();
                            if (pref != null && pref.length() > 0) {
                                return pref;
                            }
                        }
                    }
                }

            }

        }
        return null;
    }

    public static void writeTo(Node node, OutputStream out, Options options) throws IOException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = null;
        try {
            if (options.isPretty()) {
                Source stylesheetSource = new StreamSource(new ByteArrayInputStream(prettyPrintStylesheet.getBytes()));
                Templates templates = tf.newTemplates(stylesheetSource);
                t = templates.newTransformer();
            } else {
                t = tf.newTransformer();
            }
            t.setOutputProperty(OutputKeys.METHOD, "xml");
            if (options.isEmitDeclaration()) {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            } else {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }
            t.setOutputProperty(OutputKeys.ENCODING, options.getEncoding());
        } catch (TransformerConfigurationException tce) {
            assert (false);
        }
        DOMSource doms = new DOMSource(node);
        StreamResult sr = new StreamResult(out);
        try {
            t.transform(doms, sr);
        } catch (TransformerException te) {
            throw new IOException(te.getMessage());
        }
    }

    public static void writeTo(Node node, Writer out, Options options) throws IOException {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = null;
        try {
            if (options.isPretty()) {
                Source stylesheetSource = new StreamSource(new ByteArrayInputStream(prettyPrintStylesheet.getBytes()));
                Templates templates = tf.newTemplates(stylesheetSource);
                t = templates.newTransformer();
            } else {
                t = tf.newTransformer();
            }
            t.setOutputProperty(OutputKeys.METHOD, "xml");
            if (options.isEmitDeclaration()) {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            } else {
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }
            t.setOutputProperty(OutputKeys.ENCODING, options.getEncoding());
        } catch (TransformerConfigurationException tce) {
            assert (false);
        }
        DOMSource doms = new DOMSource(node);
        StreamResult sr = new StreamResult(out);
        try {
            t.transform(doms, sr);
        } catch (TransformerException te) {
            throw new IOException(te.getMessage());
        }
    }


    private static final String prettyPrintStylesheet =
            "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0' " +
                    " xmlns:xalan='http://xml.apache.org/xslt' " +
                    " exclude-result-prefixes='xalan'>" +
                    "  <xsl:output method='xml' indent='yes' xalan:indent-amount='4'/>" +
                    "  <xsl:strip-space elements='*'/>" +
                    "  <xsl:template match='/'>" +
                    "    <xsl:apply-templates/>" +
                    "  </xsl:template>" +
                    "  <xsl:template match='node() | @*'>" +
                    "        <xsl:copy>" +
                    "          <xsl:apply-templates select='node() | @*'/>" +
                    "        </xsl:copy>" +
                    "  </xsl:template>" +
                    "</xsl:stylesheet>";


}
